using System;
using System.Linq;
using System.Net.Mail;
using FoundationLib.Mail.Configuration;
using System.Configuration;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.Contracts;


namespace FoundationLib.Mail 
{
	/// <summary>
	/// This class represents a mail connector for HTML-mails.
	/// </summary>
	public class HtmlMailConnector : IMailConnector 
	{
		/// <summary>
		/// The underlying SMTP client
		/// </summary>
		private SmtpClient client;
		/// <summary>
		/// The mail connector configuration
		/// </summary>
		private MailConnectorConfigSection config;
		/// <summary>
		/// A value indicating whether the underlying smtp client is connected
		/// </summary>
		private bool isOpen;
		/// <summary>
		/// A value indicating whether the underlying smtp client has been configured
		/// </summary>
		private bool isConfigured;
		/// <summary>
		/// The log for asynchronous send operations. The elements of tuple list items are as follows:
		/// <list type="number">
		///		<item><description>The list of recipients the mail was sent to</description></item>
		///		<item><description>The date and time when the mail was sent</description></item>
		///		<item><description>A value indicating whether the mail was sent successfully</description></item>
		///		<item><description>Any additional information, for example an exception if the send operation failed</description></item>
		/// </list>
		/// </summary>
		private IList<Tuple<string, DateTime, bool, object>> asyncLog;


		/// <summary>
		/// Creates a new HTML mail connector instance.
		/// </summary>
		public HtmlMailConnector()
		{
			this.config = MailConnectorConfigSection.FromConfiguration();
			if (this.config == null)
				throw new InvalidOperationException("Could not load mail connector settings from configuration.");
			this.client = new SmtpClient();
			this.isOpen = false;
		}

		/// <summary>
		/// The object invariant.
		/// </summary>
		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.config != null);
			Contract.Invariant(this.client != null);
		}

		/// <summary>
		/// Configures the underlying SMTP client instance.
		/// </summary>
		private void Configure()
		{
			this.client.Host = this.config.Smtp.Host;
			this.client.Port = this.config.Smtp.Port;
			this.client.EnableSsl = this.config.Smtp.EnableSsl;
			this.client.UseDefaultCredentials = this.config.Smtp.UseDefaultCredentials;
			this.client.Timeout = this.config.Smtp.Timeout * 1000;

			if (!this.config.Smtp.UseDefaultCredentials)
			{
				NetworkCredential credential = new NetworkCredential();
				credential.UserName = this.config.Smtp.Username;
				credential.Password = this.config.Smtp.Password;
				this.client.Credentials = credential;
			}

			if (this.config.Smtp.EnableAsyncSend)
			{
				this.asyncLog = new List<Tuple<string, DateTime, bool, object>>();
				this.client.SendCompleted += new SendCompletedEventHandler(AsyncSendCompleted);
			}

			this.isConfigured = true;
		}

		private void AsyncSendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
		{
			this.asyncLog.Add(Tuple.Create<string, DateTime, bool, object>(
				e.UserState as string,
				DateTime.Now,
				(!e.Cancelled && e.Error == null),
				e.Cancelled ? (object)"Operation cancelled by user." : e.Error)
			);
		}

		/// <summary>
		/// Gets the asynchronous send log or null if asynchronous sending is disabled. The elements of tuple list items are as follows:
		/// <list type="number">
		///		<item><description>The list of recipients the mail was sent to</description></item>
		///		<item><description>The date and time when the mail was sent</description></item>
		///		<item><description>A value indicating whether the mail was sent successfully</description></item>
		///		<item><description>Any additional information, for example an exception if the send operation failed</description></item>
		/// </list>
		/// </summary>
		public IList<Tuple<string, DateTime, bool, object>> AsyncLog
		{
			get
			{
				return this.asyncLog;
			}
		}

		/// <summary>
		/// Closes the connector.
		/// </summary>
		public void Close()
		{
			this.client.Dispose();
			this.isOpen = false;
		}

		/// <summary>
		/// Gets a value indicating whether this connector is open and ready to send.
		/// </summary>
		public bool IsOpen
		{
			get
			{
				return this.isOpen;
			}
		}

		/// <summary>
		/// Opens the connector.
		/// </summary>
		public void Open()
		{
			this.isOpen = true;
		}

		/// <summary>
		/// Sends an email.
		/// </summary>
		/// <exception cref="ArgumentNullException">A null-reference was passed.
		/// </exception>
		/// <exception cref="ArgumentException">An argument is malformed.</exception>
		/// <param name="recipients">A list of recipient email addresses</param>
		/// <param name="subject">The mail subject</param>
		/// <param name="contents">The (HTML) mail contents</param>
		public void Send(IEnumerable<string> recipients, string subject, string contents)
		{
			if (!this.isConfigured)
				this.Configure();

			if (this.config.Debug)
				return;

			MailAddress sender = new MailAddress(this.config.Smtp.SenderEmail, this.config.Smtp.SenderName);
			MailMessage message = new MailMessage();
			message.IsBodyHtml = true;
			message.From = sender;
			message.Subject = subject;
			message.Body = contents;

			foreach (string recipient in recipients)
				message.To.Add(new MailAddress(recipient));


			if (this.config.Smtp.EnableAsyncSend)
			{
				string token = String.Join("; ", recipients);
				this.client.SendAsync(message, token);
			}
			else
			{
				this.client.Send(message);
			}
		}

		/// <summary>
		/// Disposes of this instance and releases managed resources.
		/// </summary>
		/// <param name="disposing">True if managed resources are to be released</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this.client != null)
					this.client.Dispose();
				this.client = null;
			}
		}

		/// <see cref="IDisposable.Dispose()"/>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}
	}

}